package com.ywu.application.standard.container.util;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.core.command.LogContainerResultCallback;
import com.ywu.application.standard.container.dto.*;
import com.ywu.application.standard.service.api.ServiceApi;
import com.ywu.application.standard.service.dto.ServiceDto;
import com.ywu.application.standard.service.entity.ServiceEntity;
import com.ywu.boot.context.YwuContext;
import com.ywu.boot.context.YwuContextHolder;
import com.ywu.boot.exception.YwuRuntimeException;
import com.ywu.common.gson.GsonUtils;
import com.ywu.common.mapper.YwuBeanMapper;
import com.ywu.common.result.CommonResult;
import com.ywu.application.standard.container.config.YwuContainerAppConfig;
import com.ywu.application.standard.container.entity.ServiceContainerEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

import static com.github.dockerjava.api.model.HostConfig.newHostConfig;

/**
 * @ClassName DockerClientUtils
 * @Description TODO
 * @Author GroundDemo
 * @Date 2024/3/4 21:23
 * @Version 1.0
 **/
@Component
@Slf4j
public class DockerClientUtils {
    @Autowired
    private ServiceApi serviceApi;

    @Autowired
    private YwuContainerAppConfig ywuContainerAppConfig;

    /**
    * @Author GroundDemo
    * @Description 连接Docker服务器
    * @Date 21:25 2024/3/4
    * @Param [dockerInstance]
    * @return com.github.dockerjava.api.DockerClient
    **/
    public DockerClient connectDocker(String dockerInstance) {
        DockerClient dockerClient = DockerClientBuilder.getInstance(dockerInstance).build();
        dockerClient.infoCmd().exec();
        return dockerClient;
    }

    /**
    * @Author GroundDemo
    * @Description 创建并启动容器
    * @Date 21:25 2024/3/4
    * @Param [serviceContainerDto]
    * @return com.github.dockerjava.api.command.CreateContainerResponse
    **/
    public CreateContainerResponse createContainers(ServiceContainerDto serviceContainerDto) {
        // 参数构建（将列表参数转换为docker启动时的可用字符串）
        buildDockerCmdAndVolume(serviceContainerDto);
        //连接Docker服务器
        String dockerHost = "tcp://" + serviceContainerDto.getDockerServiceAddress() + ":" + serviceContainerDto.getDockerServicePort();
        DockerClient client = connectDocker(dockerHost);
        Ports portBindings = null;
        ExposedPort sourcePort = null;
        if (!"host".equals(serviceContainerDto.getDockerNetworkMode())) {
            //映射端口withoutPort—>interiorPort
            sourcePort = ExposedPort.tcp(Integer.parseInt(serviceContainerDto.getDockerSourcePort()));
            portBindings = new Ports();
            portBindings.bind(sourcePort, Ports.Binding.bindPort(Integer.parseInt(serviceContainerDto.getDockerDestPort())));
        }
        HostConfig hostConfig = newHostConfig();
        if (Objects.nonNull(portBindings)) {
            hostConfig.withPortBindings(portBindings);
        }
        // 设置网络模式
        hostConfig.withNetworkMode(serviceContainerDto.getDockerNetworkMode());
        hostConfig.withPrivileged(true);
        // 设置挂载路径
        if (StringUtils.isNotBlank(serviceContainerDto.getDockerSourceVolume()) &&
                StringUtils.isNotBlank(serviceContainerDto.getDockerDestVolume())) {
            String[] sourceVolumes = serviceContainerDto.getDockerSourceVolume().split(",");
            String[] destVolumes = serviceContainerDto.getDockerDestVolume().split(",");
            List<Bind> binds = new ArrayList<>();
            for (int i = 0; i < sourceVolumes.length; i++) {
                binds.add(new Bind(sourceVolumes[i], new Volume(destVolumes[i])));
            }
            hostConfig.withBinds(binds);
        }
        // 设置容器开机自启动
        hostConfig.withRestartPolicy(RestartPolicy.alwaysRestart());
        // 创建容器
        CreateContainerResponse exec = null;
        if (Objects.isNull(sourcePort)) {
            exec = client.createContainerCmd(serviceContainerDto.getDockerImageId())
                    .withName(serviceContainerDto.getDockerName())
                    .withCmd(serviceContainerDto.getDockerCmd().split(" "))
                    .withHostConfig(hostConfig).exec();
        } else {
            exec = client.createContainerCmd(serviceContainerDto.getDockerImageId())
                    .withName(serviceContainerDto.getDockerName())
                    .withExposedPorts(sourcePort)
                    .withCmd(serviceContainerDto.getDockerCmd().split(" "))
                    .withHostConfig(hostConfig).exec();
        }
        try {
            client.close();
        } catch (IOException e) {
            log.error("DockerClient close error {}", e.getMessage());
        }
        return exec;
    }

    /**
    * @Author GroundDemo
    * @Description 将列表参数转换为docker启动时的可用字符串
    * @Date 11:47 2025/3/23
    * @Param [serviceContainerDto]
    * @return void
    **/
    private void buildDockerCmdAndVolume(ServiceContainerDto serviceContainerDto) {
        // 构建挂载信息
        List<ContainerVolumeDto> volumeList = serviceContainerDto.getVolumeList();
        StringBuilder sourceVolumeBuilder = new StringBuilder();
        StringBuilder destVolumeBuilder = new StringBuilder();
        boolean flag = false;
        for (ContainerVolumeDto dto : volumeList) {
            if (dto.getSource().equals(ywuContainerAppConfig.getDockerJarPath())) {
                flag = true;
            }
            sourceVolumeBuilder.append(dto.getSource()).append(",");
            destVolumeBuilder.append(dto.getDest()).append(",");
        }
        if (flag) {
            // 去除后面的逗号
            serviceContainerDto.setDockerSourceVolume(sourceVolumeBuilder.substring(0, sourceVolumeBuilder.length() - 1));
            serviceContainerDto.setDockerDestVolume(destVolumeBuilder.substring(0, destVolumeBuilder.length() - 1));
        } else {
            // 添加内置挂载目录
            sourceVolumeBuilder.append(ywuContainerAppConfig.getDockerJarPath());
            destVolumeBuilder.append(ywuContainerAppConfig.getDockerJarPath());
            serviceContainerDto.setDockerSourceVolume(sourceVolumeBuilder.toString());
            serviceContainerDto.setDockerDestVolume(destVolumeBuilder.toString());
        }
        // 构建命令信息
        StringBuilder cmdBuilder = new StringBuilder();
        cmdBuilder.append("java -jar ").append(ywuContainerAppConfig.getDockerJarPath());
        // 查询服务名称
        ServiceDto searchDto = new ServiceDto();
        searchDto.setId(serviceContainerDto.getServiceId());
        CommonResult<Object> allApplication = serviceApi.getAllApplication(searchDto);
        if (!allApplication.getCode().equals("200")) {
            throw new YwuRuntimeException("service spi query error");
        }
        String serialize = GsonUtils.serialize(allApplication.getData());
        List<ServiceEntity> deserialize = GsonUtils.deserializeList(serialize, ServiceEntity.class);
        if (CollectionUtils.isEmpty(deserialize)) {
            throw new YwuRuntimeException("service error");
        }
        ServiceEntity entity = deserialize.get(0);
        cmdBuilder.append(entity.getJarName());
        // 添加一个内置变量  com.ywu.tenantId
        YwuContext currentContext = YwuContextHolder.getCurrentContext();
        boolean tenantIdFlag = false;
        // 构建参数
        for (ContainerCmdDto dto : serviceContainerDto.getCmdList()) {
            if ("com.ywu.tenantId".equals(dto.getKey())) {
                tenantIdFlag = true;
                dto.setValue(currentContext.getYwuContextTenant().getTenantId());
            }
            cmdBuilder.append(" --").append(dto.getKey()).append("=").append(dto.getValue());
        }
        if (!tenantIdFlag) {
            ContainerCmdDto dto = new ContainerCmdDto();
            dto.setKey("com.ywu.tenantId");
            dto.setValue(currentContext.getYwuContextTenant().getTenantId());
            serviceContainerDto.getCmdList().add(dto);
            cmdBuilder.append(" --").append(dto.getKey()).append("=").append(dto.getValue());
        }
        serviceContainerDto.setDockerCmd(cmdBuilder.toString());
        serviceContainerDto.setDockerCmdListStr(GsonUtils.serialize(serviceContainerDto.getCmdList()));
    }

    /**
     * @Author GroundDemo
     * @Description 重启容器
     * @Date 21:25 2024/3/4
     * @Param [resEntity]
     * @return void
     **/
    public void restartContainer(ServiceContainerEntity resEntity) throws Exception {
        //连接Docker服务器
        String dockerHost = "tcp://" + resEntity.getDockerServiceAddress() + ":" + resEntity.getDockerServicePort();
        DockerClient client = connectDocker(dockerHost);
        client.restartContainerCmd(resEntity.getDockerContainerId()).exec();
        try {
            client.close();
        } catch (IOException e) {
            log.error("DockerClient close error {}", e.getMessage());
            throw e;
        }
    }
    
    /**
    * @Author GroundDemo
    * @Description 停止容器
    * @Date 21:25 2024/3/4
    * @Param [resEntity]
    * @return void
    **/
    public void stopContainer(ServiceContainerEntity resEntity) {
        //连接Docker服务器
        String dockerHost = "tcp://" + resEntity.getDockerServiceAddress() + ":" + resEntity.getDockerServicePort();
        DockerClient client = connectDocker(dockerHost);
        try {
            client.stopContainerCmd(resEntity.getDockerContainerId()).exec();
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        try {
            client.close();
        } catch (IOException e) {
            log.error("DockerClient close error {}", e.getMessage());
        }
    }

    /**
    * @Author GroundDemo
    * @Description 删除容器
    * @Date 21:25 2024/3/4
    * @Param [resEntity]
    * @return void
    **/
    public void removeContainer(ServiceContainerEntity resEntity) {
        //连接Docker服务器
        String dockerHost = "tcp://" + resEntity.getDockerServiceAddress() + ":" + resEntity.getDockerServicePort();
        DockerClient client = connectDocker(dockerHost);
        try {
            client.removeContainerCmd(resEntity.getDockerContainerId()).exec();
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        try {
            client.close();
        } catch (IOException e) {
            log.error("DockerClient close error {}", e.getMessage());
        }
    }

    /**
    * @Author GroundDemo
    * @Description 获取容器日志
    * @Date 0:20 2024/3/9
    * @Param [resEntity, timestamp]
    * @return void
    **/
    public List<String> getContainerLogs(ServiceContainerEntity resEntity, Long beginTimestamp, Long endTimestamp) {
        //连接Docker服务器
        String dockerHost = "tcp://" + resEntity.getDockerServiceAddress() + ":" + resEntity.getDockerServicePort();
        DockerClient client = connectDocker(dockerHost);
        final List<String> logs = new ArrayList<>();
        LogContainerCmd logContainerCmd = client.logContainerCmd(resEntity.getDockerContainerId());
        logContainerCmd.withStdOut(true).withStdErr(true);
        logContainerCmd.withSince((int) (beginTimestamp / 1000));
        logContainerCmd.withUntil((int) (endTimestamp / 1000));
        try {
            logContainerCmd.exec(new LogContainerResultCallback() {
                @Override
                public void onNext(Frame item) {
                    logs.add(item.toString());
                }
            }).awaitCompletion();
        } catch (InterruptedException e) {
            log.error("Interrupted Exception!" + e.getMessage());
        }
        return logs;
    }

    /**
    * @Author GroundDemo
    * @Description 查询容器详情
    * @Date 19:41 2024/5/20
    * @Param [dto]
    * @return void
    **/
    public void queryContainerDetails(List<ServiceContainerEntity> entityList, List<ServiceContainerDto> serviceContainerDtos) {
        // 根据address分组
        Map<String, List<ServiceContainerEntity>> groupByAddress = entityList.stream()
                .collect(Collectors.groupingBy(res -> res.getDockerServiceAddress() + ":" + res.getDockerServicePort()));
        Set<String> keys = groupByAddress.keySet();
        for (String key : keys) {
            List<ServiceContainerEntity> entities = groupByAddress.get(key);
            DockerClient client = connectDocker("tcp://" + key);
            for (ServiceContainerEntity entity : entities) {
                ServiceContainerDto dto = YwuBeanMapper.map(entity, ServiceContainerDto.class);
                InspectContainerCmd inspectContainerCmd = client.inspectContainerCmd(dto.getDockerContainerId());
                InspectContainerResponse containerInfo = inspectContainerCmd.exec();
                // 获取当前重启次数
                Integer restartCount = containerInfo.getRestartCount();
                // 获取当前容器状态
                InspectContainerResponse.ContainerState state = containerInfo.getState();
                ContainerInfoDTO containerInfoDTO = YwuBeanMapper.map(state, ContainerInfoDTO.class);
                containerInfoDTO.setRestartCount(restartCount);
                // 重置时间字符串
                containerInfoDTO.setStartedAt(resetContainerTime(containerInfoDTO.getStartedAt()));
                containerInfoDTO.setFinishedAt(resetContainerTime(containerInfoDTO.getFinishedAt()));
                log.debug("InspectContainerCmd info containerInfoDTO is {}", containerInfoDTO);
                dto.setContainerInfoDTO(containerInfoDTO);
                serviceContainerDtos.add(dto);
            }
        }
    }

    /**
    * @Author GroundDemo
    * @Description 重新设置容器返回的时间
    * @Date 21:03 2024/5/20
    * @Param [time]
    * @return java.lang.String
    **/
    private String resetContainerTime(String time) {
        String res = "";
        String before = time.split("\\.")[0].replace("T", " ");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date resDate = simpleDateFormat.parse(before);
            SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            simpleDateFormat2.setTimeZone(TimeZone.getTimeZone("GMT+8"));
            res = simpleDateFormat2.format(resDate);
        } catch (ParseException e) {
            log.error("resetContainerTime reset time error {}", e.getMessage());
            throw new YwuRuntimeException(e.getMessage());
        }
        return res;
    }

    /**
    * @Author GroundDemo
    * @Description 查询日志文件列表
    * @Date 10:56 2025/3/26
    * @Param [queryEntity]
    * @return java.util.List<com.ywu.container.standard.dto.ContainerLogDto>
    **/
    public List<ContainerLogDto> queryAppLogList(ServiceContainerEntity queryEntity) {
        String dockerHost = "tcp://" + queryEntity.getDockerServiceAddress() + ":" + queryEntity.getDockerServicePort();
        DockerClient client = connectDocker(dockerHost);

        ExecCreateCmdResponse res = client.execCreateCmd(queryEntity.getDockerContainerId())
                .withCmd("ls", "-lht", "--time-style=+%Y-%m-%d %H:%M:%S", ywuContainerAppConfig.getAppLogPath())
                .withAttachStdout(true)
                .withAttachStderr(true)
                .exec();
        try {
            // 记录读取的信息
            StringBuilder fileInfoStringBuilder = new StringBuilder();
            // 启动执行命令
            client.execStartCmd(res.getId())
                    .exec(new ExecStartResultCallback() {
                        @Override
                        public void onNext(Frame frame) {
                            fileInfoStringBuilder.append(new String(frame.getPayload()));
                        }
                    }).awaitCompletion();
            // 处理linux返回的数据并返回List对象
            return parseLinuxFileInfoList(fileInfoStringBuilder.toString());
        } catch (InterruptedException e) {
            log.error("docker exec cmd error {}", e.getMessage());
            throw new YwuRuntimeException("docker exec cmd error");
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                log.error("docker client close error {}", e.getMessage());
            }
        }
    }

    /**
    * @Author GroundDemo
    * @Description 处理linux返回的数据并返回List对象
    * @Date 11:51 2025/3/26
    * @Param [fileInfoString]
    * @return java.util.List<com.ywu.container.standard.dto.ContainerLogDto>
    **/
    private List<ContainerLogDto> parseLinuxFileInfoList(String fileInfoString) {
        List<ContainerLogDto> res = new ArrayList<>();
        String[] split = fileInfoString.split("\n");
        for (String line : split) {
            if (line.startsWith("total")) {
                continue;
            }
            // 修整空格信息
            String resLine = resetLineInfo(line);
            // 按空格分割
            String[] lineInfo = resLine.split(" ");
            // 构建对象
            ContainerLogDto containerLogDto = new ContainerLogDto();
            containerLogDto.setFileSize(lineInfo[4]);
            containerLogDto.setFileUpdateTime(lineInfo[5] + " " + lineInfo[6]);
            containerLogDto.setLogFileName(lineInfo[7]);
            res.add(containerLogDto);
        }
        return res;
    }

    /**
    * @Author GroundDemo
    * @Description 将多个空格替换为一个空格
    * @Date 12:42 2025/3/26
    * @Param [line]
    * @return java.lang.String
    **/
    private String resetLineInfo(String line) {
        StringBuilder res = new StringBuilder();
        for (int index = 1; index < line.length(); index++) {
            if (!(line.charAt(index) == ' ' && line.charAt(index -1) == ' ')) {
                res.append(line.charAt(index));
            }
        }
        return res.toString();
    }

    /**
    * @Author GroundDemo
    * @Description 下载容器中的文件至响应输出
    * @Date 12:30 2025/3/26
    * @Param [queryEntity, containerPath, localFilePath, response]
    * @return void
    **/
    public void downLoadFileResponse(ServiceContainerEntity queryEntity, String containerPath, String fileName,
                                     HttpServletResponse response) {
        String dockerHost = "tcp://" + queryEntity.getDockerServiceAddress() + ":" + queryEntity.getDockerServicePort();
        DockerClient client = connectDocker(dockerHost);
        try {
            // 创建从容器复制文件的命令
            CopyArchiveFromContainerCmd copyCmd = client.copyArchiveFromContainerCmd(queryEntity.getDockerContainerId(),
                    containerPath + fileName);
            // 执行命令并获取输入流
            InputStream is = copyCmd.exec();
            // 获取响应输出流
            // 设置response的Header
            response.setCharacterEncoding("UTF-8");
            // 指定下载文件名(attachment-以下载方式保存到本地，inline-在线预览)
            String resFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setHeader("Content-Disposition", "attachment; filename=" + resFileName + "");
            // 内容类型为通用类型，表示二进制数据流
            response.setContentType("application/octet-stream");
            try(BufferedInputStream bis = new BufferedInputStream(is);
                BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())){
                byte[] buff = new byte[2048];
                int len;
                while ((len = bis.read(buff)) != -1) {
                    bos.write(buff, 0, len);
                }
                bos.flush();
            } catch (IOException e){
                log.error("downLoadFileResponse IOException error {}", e.getMessage());
                throw new YwuRuntimeException("downLoadFileResponse IOException error");
            }
            // 关闭输入流
            is.close();
        } catch (IOException e) {
            log.error("downLoadFileResponse file download error {}", e.getMessage());
            throw new YwuRuntimeException("downLoadFileResponse file download error");
        } finally {
            // 关闭 Docker 客户端
            try {
                client.close();
            } catch (IOException e) {
                log.error("downLoadFileResponse docker client close error {}", e.getMessage());
            }
        }
    }


}
